//
//  NSData+encode.m
//  helper
//
//  Created by huangyi on 12-10-29.
//  Copyright (c) 2012年 huangyi. All rights reserved.
//

#import "NSData+encode.h"
#import <CommonCrypto/CommonCryptor.h>
#import "NSString+hash.h"
#import "NSString+path.h"
#import "NSData+hash.h"

#define defaultSecretKey "APPL"

static const char _base64EncodingTable[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const short _base64DecodingTable[256] = {
    -2, -2, -2, -2, -2, -2, -2, -2, -2, -1, -1, -2, -1, -1, -2, -2,
    -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
    -1, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, 62, -2, -2, -2, 63,
    52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -2, -2, -2, -2, -2, -2,
    -2,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
    15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -2, -2, -2, -2, -2,
    -2, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
    41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -2, -2, -2, -2, -2,
    -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
    -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
    -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
    -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
    -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
    -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
    -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
    -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2
};

static void teaenc (uint32_t* v, uint32_t* k) {
    uint32_t v0=v[0], v1=v[1], sum=0, i;           /* set up */
    uint32_t delta=0x9e3779b9;                     /* a key schedule constant */
    uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3];   /* cache key */
    for (i=0; i < 32; i++) {                       /* basic cycle start */
        sum += delta;
        v0 += ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
        v1 += ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
    }                                              /* end cycle */
    v[0]=v0; v[1]=v1;
}

static void teadec (uint32_t* v, uint32_t* k) {
    uint32_t v0=v[0], v1=v[1], sum=0xC6EF3720, i;  /* set up */
    uint32_t delta=0x9e3779b9;                     /* a key schedule constant */
    uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3];   /* cache key */
    for (i=0; i<32; i++) {                         /* basic cycle start */
        v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
        v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
        sum -= delta;
    }                                              /* end cycle */
    v[0]=v0; v[1]=v1;
}

@implementation NSData (encode)

-(NSString*)base64{
    const unsigned char * objRawData = [self bytes];
    char * objPointer;
    char * strResult;
    // Get the Raw Data length and ensure we actually have data
    int intLength = [self length];
    if (intLength == 0) return nil;
    // Setup the String-based Result placeholder and pointer within that placeholder
    strResult = (char *)calloc((((intLength + 2) / 3) * 4) + 1, sizeof(char));
    objPointer = strResult;
    // Iterate through everything
    while (intLength > 2) { // keep going until we have less than 24 bits
        *objPointer++ = _base64EncodingTable[objRawData[0] >> 2];
        *objPointer++ = _base64EncodingTable[((objRawData[0] & 0x03) << 4) + (objRawData[1] >> 4)];
        *objPointer++ = _base64EncodingTable[((objRawData[1] & 0x0f) << 2) + (objRawData[2] >> 6)];
        *objPointer++ = _base64EncodingTable[objRawData[2] & 0x3f];
        // we just handled 3 octets (24 bits) of data
        objRawData += 3;
        intLength -= 3;
    }
    // now deal with the tail end of things
    if (intLength != 0) {
        *objPointer++ = _base64EncodingTable[objRawData[0] >> 2];
        if (intLength > 1) {
            *objPointer++ = _base64EncodingTable[((objRawData[0] & 0x03) << 4) + (objRawData[1] >> 4)];
            *objPointer++ = _base64EncodingTable[(objRawData[1] & 0x0f) << 2];
            *objPointer++ = '=';
        } else {
            *objPointer++ = _base64EncodingTable[(objRawData[0] & 0x03) << 4];
            *objPointer++ = '=';
            *objPointer++ = '=';
        }
    }
    // Terminate the string-based result
    *objPointer = '\0';
    NSString *b64enc=[NSString stringWithCString:strResult encoding:NSASCIIStringEncoding];
    free(strResult);
    // Return the results as an NSString object
    return b64enc;
}

-(NSData*)aes256:(NSString*)key{
    NSData *result=nil;
	// 'key' should be 32 bytes for AES256, will be null-padded otherwise
	char keyPtr[kCCKeySizeAES256+1]; // room for terminator (unused)
	bzero(keyPtr, sizeof(keyPtr)); // fill with zeroes (for padding)
	// fetch key data
	[key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
	NSUInteger dataLength = [self length];
	//See the doc: For block ciphers, the output size will always be less than or
	//equal to the input size plus the size of one block.
	//That's why we need to add the size of one block here
	size_t bufferSize = dataLength + kCCBlockSizeAES128;
	void *buffer = malloc(bufferSize);
	size_t numBytesEncrypted = 0;
	CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
                                          keyPtr, kCCKeySizeAES256,
                                          NULL /* initialization vector (optional) */,
                                          [self bytes], dataLength, /* input */
                                          buffer, bufferSize, /* output */
                                          &numBytesEncrypted);
	if (cryptStatus == kCCSuccess) {
		//the returned NSData takes ownership of the buffer and will free it on deallocation
        result=[NSData dataWithBytes:buffer length:numBytesEncrypted];
	}
	free(buffer); //free the buffer;
	return result;
}
-(NSData*)aes256dec:(NSString*)key{
    NSData *result=nil;
	// 'key' should be 32 bytes for AES256, will be null-padded otherwise
	char keyPtr[kCCKeySizeAES256+1]; // room for terminator (unused)
	bzero(keyPtr, sizeof(keyPtr)); // fill with zeroes (for padding)
	// fetch key data
	[key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
	NSUInteger dataLength = [self length];
	//See the doc: For block ciphers, the output size will always be less than or
	//equal to the input size plus the size of one block.
	//That's why we need to add the size of one block here
	size_t bufferSize = dataLength + kCCBlockSizeAES128;
	void *buffer = malloc(bufferSize);
	size_t numBytesDecrypted = 0;
	CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
                                          keyPtr, kCCKeySizeAES256,
                                          NULL /* initialization vector (optional) */,
                                          [self bytes], dataLength, /* input */
                                          buffer, bufferSize, /* output */
                                          &numBytesDecrypted);

	if (cryptStatus == kCCSuccess) {
		//the returned NSData takes ownership of the buffer and will free it on deallocation
        result=[NSData dataWithBytes:buffer length:numBytesDecrypted];
	}
	free(buffer); //free the buffer;
	return result;
}

-(NSData*)blowfish:(NSString*)key{
    NSData *result=nil;
	char keyPtr[kCCKeySizeMinBlowfish+1];
	bzero(keyPtr, sizeof(keyPtr));
	[key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
	NSUInteger dataLength = [self length];
	size_t bufferSize = dataLength + kCCBlockSizeBlowfish;
	void *buffer = malloc(bufferSize);
	size_t numBytesEncrypted = 0;
	CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmBlowfish, kCCOptionPKCS7Padding,keyPtr, kCCKeySizeMinBlowfish,NULL,[self bytes], dataLength,buffer, bufferSize,&numBytesEncrypted);
	if (cryptStatus == kCCSuccess) {
        result=[NSData dataWithBytes:buffer length:numBytesEncrypted];
	}
	free(buffer);
	return result;
}
-(NSData*)blowfishdec:(NSString*)key{
    NSData *result=nil;
	char keyPtr[kCCKeySizeMinBlowfish+1];
	bzero(keyPtr, sizeof(keyPtr));
	[key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
	NSUInteger dataLength = [self length];
	size_t bufferSize = dataLength + kCCBlockSizeBlowfish;
	void *buffer = malloc(bufferSize);
	size_t numBytesDecrypted = 0;
	CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt, kCCAlgorithmBlowfish, kCCOptionPKCS7Padding,keyPtr, kCCKeySizeMinBlowfish,NULL,[self bytes], dataLength,buffer, bufferSize,&numBytesDecrypted);
	if (cryptStatus == kCCSuccess) {
        result=[NSData dataWithBytes:buffer length:numBytesDecrypted];
	}
	free(buffer);
	return result;
}

-(NSData*)des:(NSString*)key{
    NSData *result=nil;
	char keyPtr[kCCKeySizeDES+1];
	bzero(keyPtr, sizeof(keyPtr));
	[key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
	NSUInteger dataLength = [self length];
	size_t bufferSize = dataLength + kCCBlockSizeDES;
	void *buffer = malloc(bufferSize);
	size_t numBytesEncrypted = 0;
	CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmDES, kCCOptionPKCS7Padding,keyPtr, kCCKeySizeDES,NULL,[self bytes], dataLength,buffer, bufferSize,&numBytesEncrypted);
	if (cryptStatus == kCCSuccess) {
        result=[NSData dataWithBytes:buffer length:numBytesEncrypted];
	}
	free(buffer);
	return result;
}
-(NSData*)desdec:(NSString*)key{
    NSData *result=nil;
	char keyPtr[kCCKeySizeDES+1];
	bzero(keyPtr, sizeof(keyPtr));
	[key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
	NSUInteger dataLength = [self length];
	size_t bufferSize = dataLength + kCCBlockSizeDES;
	void *buffer = malloc(bufferSize);
	size_t numBytesDecrypted = 0;
	CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt, kCCAlgorithmDES, kCCOptionPKCS7Padding,keyPtr, kCCKeySizeDES,NULL,[self bytes], dataLength,buffer, bufferSize,&numBytesDecrypted);
	if (cryptStatus == kCCSuccess) {
        result=[NSData dataWithBytes:buffer length:numBytesDecrypted];
	}
	free(buffer);
	return result;
}

-(NSData*)rc4:(NSString*)key{
    NSData *result=nil;
	char keyPtr[kCCKeySizeMaxRC4+1];
	bzero(keyPtr, sizeof(keyPtr));
	[key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
	NSUInteger dataLength = [self length];
	size_t bufferSize = dataLength;
	void *buffer = malloc(bufferSize);
	size_t numBytesEncrypted = 0;
	CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmRC4, kCCOptionPKCS7Padding,keyPtr, kCCKeySizeMaxRC4,NULL,[self bytes], dataLength,buffer, bufferSize,&numBytesEncrypted);
	if (cryptStatus == kCCSuccess) {
        result=[NSData dataWithBytes:buffer length:numBytesEncrypted];
	}
	free(buffer);
	return result;
}
-(NSData*)rc4dec:(NSString*)key{
    NSData *result=nil;
	char keyPtr[kCCKeySizeMaxRC4+1];
	bzero(keyPtr, sizeof(keyPtr));
	[key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
	NSUInteger dataLength = [self length];
	size_t bufferSize = dataLength;
	void *buffer = malloc(bufferSize);
	size_t numBytesDecrypted = 0;
	CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt, kCCAlgorithmRC4, kCCOptionPKCS7Padding,keyPtr, kCCKeySizeMaxRC4,NULL,[self bytes], dataLength,buffer, bufferSize,&numBytesDecrypted);
	if (cryptStatus == kCCSuccess) {
        result=[NSData dataWithBytes:buffer length:numBytesDecrypted];
	}
	free(buffer);
	return result;
}

-(NSData*)encrypt{
    NSString *key=[NSString stringWithUTF8String:defaultSecretKey];
    NSString *skey=key.sha1.base64;
    return [self rc4:skey];
}
-(NSData*)decrypt{
    NSString *key=[NSString stringWithUTF8String:defaultSecretKey];
    NSString *skey=key.sha1.base64;
    return [self rc4dec:skey];
}

@end

extern NSString* file_enc_rc4(NSString *inPath){
    NSString *outPath=inPath.tempPath;
    NSFileHandle *inhandle=[NSFileHandle fileHandleForReadingAtPath:inPath];
    [inhandle seekToFileOffset:0];
    NSFileHandle *outhandle=[NSFileHandle fileHandleForWritingAtPath:outPath];
    [outhandle truncateFileAtOffset:0];
    BOOL eof=NO;
    NSUInteger length=256000;
    while (!eof) {
        @autoreleasepool {
            NSData *data=[inhandle readDataOfLength:length];
            if (data) {
                [outhandle writeData:data.encrypt];
            }else{
                eof=YES;
            }
        }
    }
    [inhandle closeFile];
    [outhandle closeFile];
    return outPath;
}
extern NSString* file_dec_rc4(NSString *inPath){
    NSString *outPath=inPath.tempPath;
    NSFileHandle *inhandle=[NSFileHandle fileHandleForReadingAtPath:inPath];
    [inhandle seekToFileOffset:0];
    NSFileHandle *outhandle=[NSFileHandle fileHandleForWritingAtPath:outPath];
    [outhandle truncateFileAtOffset:0];
    BOOL eof=NO;
    NSUInteger length=256000;
    while (!eof) {
        @autoreleasepool {
            NSData *data=[inhandle readDataOfLength:length];
            if (data) {
                [outhandle writeData:data.decrypt];
            }else{
                eof=YES;
            }
        }
    }
    [inhandle closeFile];
    [outhandle closeFile];
    return outPath;
}
